#pragma once

#include "Types.hpp"
#include "Data.hpp"
#include <map>

class VariableManager
{
public:
	VariableManager();
	~VariableManager();
	void StartPreprocessing();
	void StartWorking();

	bool Find(const string &name, Type *outType, bool *outConst);
	bool FindLocal(const string &name, Type *outType, bool *outConst);
	void * AccessValue(const string &name);
	void * New(const string &name, Type type, bool Const);

	void BeginBlock();
	void EndBlock();

private:
	struct VarData
	{
		Type VarType;
		bool Const;
		std::vector<BYTE> Data;

		VarData(Type type, bool Const, bool allocData);
		~VarData();
	};
	typedef shared_ptr<VarData> VarPtr;
	typedef std::map<string, VarPtr> VarMap;
	typedef shared_ptr<VarMap> VarMapPtr;
	std::vector<VarMapPtr> m_Stack;
	// false - preprocessing, true - working.
	bool m_Working;

	// If not found, returns NULL.
	VarData * FindVar(const string &name);
	VarData * FindVarLocal(const string &name);
	void AddMathConstants();
};

struct RegisteredFunction
{
	Type ResultType;
	std::vector<Type> ParamTypes;
	uint NoDefaultParamCount;

	virtual ~RegisteredFunction() { }
	virtual void Exec(void *out, void **params, uint paramCount) = 0;
};

typedef shared_ptr<RegisteredFunction> RegisteredFunctionPtr;

struct RegisteredRegScriptFunction : public RegisteredFunction
{
	FunctionDef *Def;

	// Automatically fills signature description in fields of RegisterFunction base class.
	RegisteredRegScriptFunction(FunctionDef *def);
	virtual void Exec(void *out, void **params, uint paramCount);
};

struct RegisteredIntrinsicFunction : public RegisteredFunction
{
	enum FUNC
	{
		FUNC_SIN,
		FUNC_PRINT,
		FUNC_COUNT
	};
	static const char * FUNC_NAMES[];

	FUNC Func;

	// Automatically fills signature description in fields of RegisterFunction base class.
	RegisteredIntrinsicFunction(FUNC func);
	virtual void Exec(void *out, void **params, uint paramCount);
};

class FunctionManager
{
public:
	// If collision with existing one, throws exception.
	void RegisterIntrinsic();
	
	enum FIND_RESULT
	{
		FIND_RESULT_OK,
		FIND_RESULT_NOT_FOUND,
		FIND_RESULT_PARAMS_NOT_MATCH,
		FIND_RESULT_AMBIGUOUS,
		FIND_RESULT_COUNT
	};

	void Register(const string &name, RegisteredFunctionPtr registeredFunc);
	// Given name and actual parameter types, tries to find matching function.
	FIND_RESULT Find(const string &name, RegisteredFunction *&outRegisteredFunc, const Type *paramTypes, uint paramCount, const TypeConvRules &rules);
	FIND_RESULT FindExactMatch(RegisteredFunction *&outRegisteredFunc, const string &name, Type resultType, const Type *paramTypes, uint paramCount);
	// Returns multiline string with each line containing signature of a function with given name.
	void GetSignatures(string &out, const string &name, const string *prefix = NULL);
	// Fills outSuggestions array with actualParamCount suggestions for parameter types based on all suitable overloaded versions of function with given name,
	// If no single suitable function could be found, returns false.
	bool SuggestTypes(std::vector<TypeSuggestion> &outSuggestions, const string &funcName, uint actualParamCount);

	//void Eval(void *out, uint id, void **params);
	//void EvalVariadic(void *out, uint id, void **params, const Type *paramTypes, uint paramCount);

private:
	static bool SignaturesConflict(RegisteredFunction *func1, RegisteredFunction *func2);
	static bool SignatureMatch(uint &outParamConversionCount, RegisteredFunction *func, const Type *paramTypes, uint paramCount, const TypeConvRules &rules);
	static bool SignatureExactMatch(RegisteredFunction *func, Type resultType, const Type *paramTypes, uint paramCount);

	typedef std::multimap<string, RegisteredFunctionPtr> RegisteredFunctionMap;
	RegisteredFunctionMap m_RegisteredFunctions;
};

class Context
{
public:
	VariableManager VarMan;
	FunctionManager FuncMan;

	void StartPreprocessing();
	void StartWorking() { VarMan.StartWorking(); }

private:
};

extern Context g_Context;

class ContextBlock
{
public:
	ContextBlock() { g_Context.VarMan.BeginBlock(); }
	~ContextBlock() { g_Context.VarMan.EndBlock(); }
};

class PreprocessContext
{
public:
	TypeConvRules Rules;

	static const uint FLAG_ENABLE_BREAK    = 0x01;
	static const uint FLAG_ENABLE_CONTINUE = 0x02;

	class FlagsObj
	{
		PreprocessContext &ctx;
	public:
		FlagsObj(PreprocessContext &ctx, uint flags) : ctx(ctx) { ctx.PushFlags(flags); }
		~FlagsObj() { ctx.PopFlags(); }
	};

	Type ResultType; // TYPE_COUNT if not currently inside a function or statement block.

	PreprocessContext();

	void PushFlags(uint flags) { m_FlagStack.push_back(m_FlagStack.back() | flags); }
	void PopFlags() { assert(!m_FlagStack.empty()); m_FlagStack.pop_back(); }
	bool CheckFlag(uint flag) { return (m_FlagStack.back() & flag) != 0; }

private:
	std::vector<uint> m_FlagStack;
};
